package client_core

import (
	"context"
	"errors"
	"github.com/chicken-team-outside/chicken_transmission/model"
	"github.com/chicken-team-outside/chicken_transmission/utils"
	"go.uber.org/zap"
	"os"
)

type ReadDirCB func(task *model.Task, dirPath string) ([]os.FileInfo, error)
type RunTaskCB func(data *TaskWarp) error
type DeleteTaskCB func(data *model.Task) error

type TaskProgress struct {
	Id        int64 `json:"id"`
	Completed int64 `json:"completed"`
	UpdateAt  int64 `json:"update_at"`
}

type TaskRunnerMgr struct {
	fileTaskType model.TaskType
	dirTaskType  model.TaskType
	fileRunner   *TaskRunner
	dirRunner    *TaskRunner
	isDownload   bool
}

func (m *TaskRunnerMgr) Startup(dirTaskMaxWorkers, fileTaskMaxWorkers int) {
	m.fileRunner.Startup(fileTaskMaxWorkers)
	m.dirRunner.Startup(dirTaskMaxWorkers)
}

func (m *TaskRunnerMgr) AddTasks(query *AddTaskQuery) error {
	var dirTask []*model.Task
	var filesTask []*model.Task
	for _, item := range query.Files {
		task := &model.Task{
			Status:     model.TaskStatus_Waiting,
			Total:      item.Size,
			DevicePath: utils.ConcatPath(query.DevicePath, item.Name),
			DeviceId:   query.DeviceID,
			LocalPath:  utils.ConcatPath(query.LocalPath, item.Name),
		}
		if item.IsDir {
			task.TaskType = m.dirTaskType
			dirTask = append(dirTask, task)
		} else {
			task.TaskType = m.fileTaskType
			filesTask = append(filesTask, task)
		}
	}
	err01 := m.fileRunner.AddTasks(filesTask)
	if err01 != nil {
		zap.L().Error("failed to add file tasks", zap.Error(err01))
	}
	err02 := m.dirRunner.AddTasks(dirTask)
	if err02 != nil {
		zap.L().Error("failed to add dir tasks", zap.Error(err02))
	}

	if err01 != nil || err02 != nil {
		return errors.New("add tasks occur error")
	}
	return nil
}

func (m *TaskRunnerMgr) Shutdown() {
	m.fileRunner.Shutdown()
	m.dirRunner.Shutdown()
}

func (m *TaskRunnerMgr) sliceTaskIds(ids []*model.TaskId) (fileIds []int64, dirIds []int64) {
	for _, item := range ids {
		if item.TaskType == m.fileTaskType {
			fileIds = append(fileIds, item.Id)
		} else {
			dirIds = append(dirIds, item.Id)
		}
	}
	return
}

func (m *TaskRunnerMgr) opResolve(ids []*model.TaskId, fileUploadFunc, dirUploadFunc, fileDownloadFunc, dirDownloadFunc func([]int64) error) (err error) {
	if m.isDownload {
		fileIds, dirIds := m.sliceTaskIds(ids)
		if len(fileIds) > 0 {
			err = fileDownloadFunc(fileIds)
		}
		if err == nil && len(dirIds) > 0 {
			err = dirDownloadFunc(dirIds)
		}
	} else {
		fileIds, dirIds := m.sliceTaskIds(ids)
		if len(fileIds) > 0 {
			err = fileUploadFunc(fileIds)
		}
		if err == nil && len(dirIds) > 0 {
			err = dirUploadFunc(dirIds)
		}
	}
	return
}

func (m *TaskRunnerMgr) PauseTask(ids []*model.TaskId) (err error) {
	err = m.opResolve(ids, m.pauseUploadTaskCb, m.pauseUploadDirTaskCb, m.pauseDownloadTaskCb, m.pauseDownloadDirTask)
	return
}

func (m *TaskRunnerMgr) ResumeTask(ids []*model.TaskId) (err error) {
	err = m.opResolve(ids, m.resumeUploadTaskCb, m.resumeUploadDirTaskCb, m.resumeDownloadTaskCb, m.resumeDownloadDirTask)
	return
}

func (m *TaskRunnerMgr) DeleteTask(ids []*model.TaskId) (err error) {
	err = m.opResolve(ids, m.deleteUploadTaskCb, m.deleteUploadDirTaskCb, m.deleteDownloadTaskCb, m.deleteDownloadDirTask)
	return
}

func (m *TaskRunnerMgr) GetRunningTaskStatus() (result []*TaskProgress) {
	result = m.fileRunner.GetRunningTaskStatus()
	result = append(result, m.dirRunner.GetRunningTaskStatus()...)
	return
}

func NewTaskRunnerMgr(ctx context.Context, isDownload bool) *TaskRunnerMgr {
	mgr := &TaskRunnerMgr{isDownload: isDownload}
	if isDownload {
		mgr.dirTaskType = model.TaskType_DownloadDir
		mgr.fileTaskType = model.TaskType_DownloadFile
		mgr.dirRunner = NewTaskRunner(ctx, mgr.dirTaskType, mgr.runDownloadDirScanTask)
		mgr.fileRunner = NewTaskRunner(ctx, mgr.fileTaskType, mgr.runDownloadTaskCb)
	} else {
		mgr.dirTaskType = model.TaskType_UploadDir
		mgr.fileTaskType = model.TaskType_UploadFile
		mgr.dirRunner = NewTaskRunner(ctx, mgr.dirTaskType, mgr.runUploadDirScanTask)
		mgr.fileRunner = NewTaskRunner(ctx, mgr.fileTaskType, mgr.runUploadTask)
	}
	return mgr
}
